package 日程提醒工具.controller;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import 日程提醒工具.model.DoneSchedule;
import 日程提醒工具.model.Schedule;
import 日程提醒工具.model.UndoneSchedule;
import 日程提醒工具.view.ConsoleView;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ScheduleReminder {
    private final String PATH_OF_DONE_SCHEDULE = "src/main/java/日程提醒工具/resources/done_schedule.jsonl";
    private final String PATH_OF_UNDONE_SCHEDULE = "src/main/java/日程提醒工具/resources/undone_schedule.jsonl";
    private final ConsoleView view;
    private static final ObjectMapper mapper = new ObjectMapper().registerModule(new JavaTimeModule()).configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    private List<DoneSchedule> cacheOfDoneSchedules;    // 本次程序运行时新添加的已完成任务
    private List<UndoneSchedule> cacheOfUndoneSchedules;    // 所有的未完成任务
    private final ScheduledExecutorService scheduledExecutor = Executors.newScheduledThreadPool(1);

    public ScheduleReminder(ConsoleView view) {
        this.view = view;
    }

    public void initialize() throws IOException {
        // 加载配置文件
        cacheOfDoneSchedules = new ArrayList<>();   // 本次程序运行时新添加的已完成任务
        cacheOfUndoneSchedules = loadAllUndoneSchedules();

        // 启动守护进程，自动提醒已过期任务
        overdueRemind();
    }

    public void save() throws IOException {
        // 在保存新的未完成任务的缓存之前，先清空指定文件
        clearFile(PATH_OF_UNDONE_SCHEDULE);

        // 保存
        for (DoneSchedule cacheOfDoneSchedule : cacheOfDoneSchedules) {
            writeObjectToJson(cacheOfDoneSchedule, PATH_OF_DONE_SCHEDULE);
        }
        for (UndoneSchedule cacheOfUndoneSchedule : cacheOfUndoneSchedules) {
            writeObjectToJson(cacheOfUndoneSchedule, PATH_OF_UNDONE_SCHEDULE);
        }

        // 程序退出，自动关闭线程池
        scheduledExecutor.shutdown();
    }

    private void clearFile(String path) throws IOException {
        // 清空指定文件
        try (FileWriter writer = new FileWriter(path, false)) {
            writer.write("");
        }
    }

    /**
     * 按照完成时间排序已完成任务(不用手动排序，自然排序)
     * 按照任务截止时间排序未完成任务(时间越早，排序越靠前)
     */
    private void sortUndoneSchedule() {
        cacheOfUndoneSchedules.sort((a, b) -> {
            if (isAfter(a, b)) return 1;
            if (isAfter(b, a)) return -1;
            return 0; // 相等
        });
    }

    private boolean isAfter(Schedule one, Schedule another) {
        return one.getDeadline().isAfter(another.getDeadline());
    }

    /**
     * 对于已完成的任务，直接自然保存，即直接将新增内容添加到原存储文件；对于未完成任务，则先按照截止时间排序，再覆盖原存储文件内容。
     *
     * @param schedule 要保存的任务
     * @param path     保存到的路径
     * @return 是否保存成功
     */
    private boolean writeObjectToJson(Schedule schedule, String path) {
        try {
            String json = mapper.writeValueAsString(schedule);

            try (BufferedWriter writer = new BufferedWriter(new FileWriter(path, StandardCharsets.UTF_8, true))) {
                writer.write(json);
                writer.newLine();
            }
            return true;
        } catch (IOException e) {
            System.err.println("保存失败!");
            return false;
        }
    }

    private void setId() {
        for (int i = 0; i < cacheOfUndoneSchedules.size(); i++) {
            cacheOfUndoneSchedules.get(i).setId(i + 1);
        }
    }

    public void addSchedule() {
        UndoneSchedule schedule = new UndoneSchedule();

        schedule.setTitle(view.inputTitle());
        schedule.setDescription(view.inputDescription());
        schedule.setDeadline(view.inputDeadline());
        cacheOfUndoneSchedules.add(schedule);   // 日程修改仅限于系统的缓存，即将退出系统时自动持久化保存，减少IO浪费的时间。
        System.out.println("任务添加成功！");
        System.out.println();

        sortUndoneSchedule();   // 添加完任务直接重新排序
        setId();    // 根据排序结果重新设置序号
    }

    private List<UndoneSchedule> loadAllUndoneSchedules() throws IOException {
        List<UndoneSchedule> schedules = new ArrayList<>();

        // 读取指定文件
        try (BufferedReader reader = new BufferedReader(new FileReader(PATH_OF_UNDONE_SCHEDULE))) {
            String line;
            while ((line = reader.readLine()) != null) {
                try {
                    schedules.add(mapper.readValue(line, UndoneSchedule.class));
                } catch (IOException e) {
                    System.err.println("跳过损坏记录: " + line);
                }
            }
        }

        return schedules;
    }

    private List<DoneSchedule> loadAllDoneSchedules() throws IOException {
        List<DoneSchedule> schedules = new ArrayList<>();
        try (BufferedReader reader = new BufferedReader(new FileReader(PATH_OF_DONE_SCHEDULE))) {
            String line;
            while ((line = reader.readLine()) != null) {
                try {
                    schedules.add(mapper.readValue(line, DoneSchedule.class));
                } catch (IOException e) {
                    System.err.println("跳过损坏记录: " + line);
                }
            }
        }
        return schedules;
    }

    private void printUndoneSchedule(UndoneSchedule schedule) {
        System.out.println("序号：" + schedule.getId());
        System.out.println("标题：" + schedule.getTitle());
        System.out.println("描述：" + schedule.getDescription());
        System.out.println("截止日期：" + schedule.getDeadline().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")));
        System.out.println();
    }

    private void printDoneSchedule(DoneSchedule schedule) {
        System.out.println("标题：" + schedule.getTitle());
        System.out.println("描述：" + schedule.getDescription());
        System.out.println("截止日期：" + schedule.getDeadline().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")));
        System.out.println("完成时间：" + schedule.getDoneTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")));
        System.out.println();
    }

    public void getUndoneSchedules() {
        for (UndoneSchedule schedule : cacheOfUndoneSchedules) {
            printUndoneSchedule(schedule);
        }
    }

    public void getDoneSchedules() throws IOException {
        // 只有在需要查看已完成任务时才获取全部已完成任务（因为目前系统只有这个时候才需要已完成任务的历史记录）。
        List<DoneSchedule> scheduleList = loadAllDoneSchedules();
        scheduleList.addAll(cacheOfDoneSchedules);
        for (DoneSchedule schedule : scheduleList) {
            printDoneSchedule(schedule);
        }
    }

    public void manageSchedule() {
        int id = view.inputId();

        for (int i = 0; i < cacheOfUndoneSchedules.size(); i++) {
            UndoneSchedule schedule = cacheOfUndoneSchedules.get(i);

            if (schedule.getId() == id) {
                System.out.println("已找到！");
                System.out.print("是否将此任务设置为已完成?(yes/no):");
                if (view.yesOrNo()) {
                    schedule.setDone(true);
                    cacheOfUndoneSchedules.remove(i);
                    cacheOfDoneSchedules.add(new DoneSchedule(schedule));
                    System.out.println("设置成功！恭喜完成任务！");
                    System.out.println();
                    return;
                }
                System.out.println();
                return;
            }
        }
        System.out.println("未找到指定任务！");
        System.out.println();
    }

    private void overdueRemind() {
        Thread reminder = new Thread(() -> {
            boolean found = false;
            for (UndoneSchedule schedule : cacheOfUndoneSchedules) {
                if (schedule.getDeadline().isBefore(LocalDateTime.now())) {
                    System.out.println();
                    System.out.println("[提醒] 任务\"" + schedule.getTitle() + "\"已过期！截止时间为" + schedule.getDeadline().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")));
                    found = true;
                }
            }
            // 防止在没有任务过期的情况下打印提示符
            if (found){
                view.showInputReminder();
            }
        });
        reminder.setDaemon(true);

        scheduledExecutor.scheduleAtFixedRate(reminder, 30, 30, TimeUnit.SECONDS);
    }
}
